package base

import (
	"context"
	"errors"
	"fmt"
	"go-project/common/base/req"

	"github.com/samber/lo"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Dao[T any] struct{}

// Page query = *T | map[string]any
func (dao *Dao[T]) Page(db *gorm.DB, page *req.Pager, query any) ([]*T, int64, error) {
	tx := gorm.G[T](db)
	ctx := context.Background()
	offset := page.GetOffset()
	count, err := tx.Where(query).Count(ctx, "*")
	if err != nil {
		return nil, 0, err
	}
	if count == 0 {
		return []*T{}, 0, nil
	}
	list, err := tx.Offset(offset).Limit(page.PageSize).Where(query).Order(clause.OrderByColumn{
		Column: clause.Column{Name: page.Order},
		Desc:   !page.Asc,
	}).Find(ctx)
	if err != nil {
		return nil, 0, err
	}
	return lo.Map(list, func(item T, _ int) *T { return &item }), count, nil
}

func (dao *Dao[T]) GetById(db *gorm.DB, id any) (*T, error) {
	ent, err := gorm.G[T](db).Where("id = ?", id).Take(context.Background())
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &ent, nil
}

func (dao *Dao[T]) Save(db *gorm.DB, ent *T) error {
	if ent == nil {
		return errors.New("entity cannot be nil")
	}
	return db.Save(ent).Error
}

func (dao *Dao[T]) DeleteById(db *gorm.DB, id any) error {
	if id == nil {
		return errors.New("id cannot be nil")
	}
	_, err := gorm.G[T](db).Where("id = ?", id).Delete(context.Background())
	return err
}

// GetOne query = *T | map[string]any
func (dao *Dao[T]) GetOne(db *gorm.DB, query any) (*T, error) {
	ent, err := gorm.G[T](db).Where(query).Take(context.Background())
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &ent, nil
}

// GetAll query = *T | map[string]any
func (dao *Dao[T]) GetAll(db *gorm.DB, query any) ([]*T, error) {
	list, err := gorm.G[T](db).Where(query).Find(context.Background())
	if err != nil {
		return nil, err
	}
	return lo.Map(list, func(item T, _ int) *T { return &item }), nil
}

// CreateInBatches 批量创建 - 支持值类型切片
func (dao *Dao[T]) CreateInBatches(db *gorm.DB, entList []T, batchSize int) error {
	if len(entList) == 0 {
		return nil
	}

	if batchSize <= 0 {
		batchSize = 100
	}

	if err := gorm.G[T](db).CreateInBatches(context.Background(), &entList, batchSize); err != nil {
		return err
	}
	return nil
}

// CreateInBatchesPtr 批量创建 - 支持指针类型切片
func (dao *Dao[T]) CreateInBatchesPtr(db *gorm.DB, entList []*T, batchSize int) error {
	if len(entList) == 0 {
		return nil
	}

	if batchSize <= 0 {
		batchSize = 100
	}

	list := lo.Map(entList, func(item *T, _ int) T { return *item })

	if err := gorm.G[T](db).CreateInBatches(context.Background(), &list, batchSize); err != nil {
		return err
	}
	return nil
}

func (dao *Dao[T]) BatchDelete(db *gorm.DB, ids []any) error {
	if len(ids) == 0 {
		return nil
	}
	_, err := gorm.G[T](db).Where("id IN ?", ids).Delete(context.Background())
	if err != nil {
		return err
	}
	return nil
}

// Updates query = *T | map[string]any
func (dao *Dao[T]) Updates(db *gorm.DB, query any, updates T) error {
	_, err := gorm.G[T](db).Where(query).Updates(context.Background(), updates)
	if err != nil {
		return err
	}
	return nil
}

// Exists 检查记录是否存在
func (dao *Dao[T]) Exists(db *gorm.DB, query any) (bool, error) {
	count, err := gorm.G[T](db).Where(query).Count(context.Background(), "*")
	if err != nil {
		return false, fmt.Errorf("failed to check existence: %w", err)
	}
	return count > 0, nil
}
